/**
 * 
 */
package com.alphasystem.commerce.common.service.impl;

import static java.lang.Integer.MAX_VALUE;
import static org.apache.commons.lang.StringUtils.isBlank;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;

import org.apache.log4j.Logger;

import com.alphasystem.commerce.common.ApplicationException;
import com.alphasystem.commerce.common.SystemErrorCode;
import com.alphasystem.commerce.common.SystemException;
import com.alphasystem.commerce.common.model.AbstractEntity;
import com.alphasystem.commerce.common.model.AbstractEntity_;
import com.alphasystem.commerce.common.service.ServiceFacade;
import com.alphasystem.commerce.common.service.sequence.SequenceService;
import com.alphasystem.commerce.common.valuelist.ValueListObject;

/**
 * @author sali
 * 
 */
public abstract class AbstractServiceFacade<T extends AbstractEntity, M extends AbstractEntity_, V extends ValueListObject>
		implements ServiceFacade<T, M, V> {

	protected EntityManager entityManager;

	protected transient Logger logger = Logger.getLogger(getClass());

	protected Class<T> persistentClass;

	protected Class<M> metaModelClass;

	protected Class<V> valueListClass;

	protected String sequenceName;

	protected SequenceService sequenceService;

	protected Query countQuery;

	protected TypedQuery<T> singleEagerQuery;

	protected TypedQuery<T> findAllEagerQuery;

	protected TypedQuery<T> findAllLazyQuery;

	protected TypedQuery<V> valueListQuery;

	protected void addLazyFields(Root<T> root) {
	}

	@Override
	public T create() throws ApplicationException {
		return create(null);
	}

	@Override
	public T create(String id) throws ApplicationException {
		T entity = null;
		try {
			entity = persistentClass.newInstance();
			if (isBlank(id)) {
				id = sequenceService.getNextSequenceNumber(sequenceName);
			}
			entity.setId(id);
		} catch (InstantiationException e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(new SystemErrorCode("SYSTEM_ERROR"));
		} catch (IllegalAccessException e) {
			logger.error(e.getMessage(), e);
			throw new SystemException(new SystemErrorCode("SYSTEM_ERROR"));
		}
		return entity;
	}

	/**
	 * @param eager
	 *            &mdash; indicates whether query retrieves Collection based
	 *            fields eagerly
	 * @param single
	 *            &mdash; indicates whether query retrieves single result
	 * @return
	 */
	protected TypedQuery<T> createQuery(boolean eager, boolean single) {
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<T> cq = cb.createQuery(persistentClass);
		Root<T> root = cq.from(persistentClass);
		if (eager) {
			addLazyFields(root);
			cq = cq.distinct(true);
		}
		cq = cq.select(root);
		if (single) {
			cq = cq.where(cb.equal(root.get(M.id),
					cb.parameter(M.id.getType().getJavaType())));
		}
		return entityManager.createQuery(cq);
	}

	protected void doStartService() {
	}

	protected List<T> executeQuery(TypedQuery<T> query, int from, int max) {
		if (from <= -1) {
			from = 0;
		}
		query.setFirstResult(from);
		if (max <= -1) {
			max = MAX_VALUE;
		}
		query.setMaxResults(max);
		return query.getResultList();
	}

	@Override
	public List<T> findAll() throws ApplicationException {
		return findAll(false);
	}

	@Override
	public List<T> findAll(boolean eager) throws ApplicationException {
		return findAll(eager, -1, -1);
	}

	@Override
	public List<T> findAll(boolean eager, int from, int max)
			throws ApplicationException {
		TypedQuery<T> query = eager ? findAllEagerQuery : findAllLazyQuery;
		return executeQuery(query, from, max);
	}

	@Override
	public List<T> findAll(int from, int max) throws ApplicationException {
		return findAll(false, from, max);
	}

	@Override
	public T findById(String id) throws ApplicationException {
		return findById(id, false);
	}

	@Override
	public T findById(String id, boolean eager) throws ApplicationException {
		T entity = null;
		if (eager) {
			singleEagerQuery.setParameter(M.id.getName(), id);
			List<T> list = executeQuery(singleEagerQuery, -1, -1);
			if (list != null && !list.isEmpty()) {
				entity = list.get(0);
			}
		} else {
			entity = entityManager.find(persistentClass, id);
		}
		return entity;
	}

	@Override
	public long getCount() throws ApplicationException {
		Object obj = countQuery.getSingleResult();
		long count = -1;
		if (obj != null) {
			if (obj instanceof Long) {
				count = (Long) obj;
			} else {
				count = Long.parseLong(obj.toString());
			}
		}
		return count;
	}

	public Query getCountQuery() {
		return countQuery;
	}

	/**
	 * @return the entityManager
	 */
	public EntityManager getEntityManager() {
		return entityManager;
	}

	/**
	 * @return
	 */
	protected Class<M> getMetaModelClass() {
		return metaModelClass;
	}

	/**
	 * @return the persistentClass
	 */
	public Class<T> getPersistentClass() {
		return persistentClass;
	}

	@Override
	public SequenceService getSequenceService() {
		return sequenceService;
	}

	@Override
	public List<V> getValueList() throws ApplicationException {
		return getValueList(-1, -1);
	}

	@Override
	public List<V> getValueList(int from, int max) throws ApplicationException {
		if (from <= -1) {
			from = 0;
		}
		valueListQuery.setFirstResult(from);
		if (max <= -1) {
			max = MAX_VALUE;
		}
		valueListQuery.setMaxResults(max);
		return valueListQuery.getResultList();
	}

	/**
	 * @return the valueListClass
	 */
	public Class<V> getValueListClass() {
		return valueListClass;
	}

	protected Selection<?>[] getValueListPropertiesSelections(Root<T> root) {
		return new Selection<?>[] { root.get(M.id), root.get(M.id) };
	}

	protected void initCountQuery() {
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<Long> cq = cb.createQuery(Long.class);
		cq = cq.select(cb.count(cq.from(persistentClass)));
		countQuery = entityManager.createQuery(cq);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected void initGenericClases() {
		Class klass = getClass();
		while (true) {
			Type type = klass.getGenericSuperclass();
			if (type == null) {
				throw new RuntimeException("Could not find generic type: "
						+ getClass().getName());
			}
			if (type instanceof ParameterizedType) {
				ParameterizedType parameterizedType = (ParameterizedType) type;
				Type[] types = parameterizedType.getActualTypeArguments();
				persistentClass = (Class<T>) types[0];
				metaModelClass = (Class<M>) types[1];
				valueListClass = (Class<V>) types[2];
				break;
			}
			klass = klass.getSuperclass();
			if (klass == null) {
				throw new RuntimeException("Could not find generic type: "
						+ getClass().getName());
			}
		}
	}

	protected void initValueListQuery() {
		CriteriaBuilder cb = entityManager.getCriteriaBuilder();
		CriteriaQuery<V> cq = cb.createQuery(valueListClass);
		Root<T> root = cq.from(persistentClass);
		cq = cq.select(cb.construct(valueListClass,
				getValueListPropertiesSelections(root)));
		valueListQuery = entityManager.createQuery(cq);
	}

	protected void postStartService() {
		sequenceName = sequenceService.getSequenceName(persistentClass);
	}

	protected void preStartService() {
		initGenericClases();
		initCountQuery();
		singleEagerQuery = createQuery(true, true);
		findAllEagerQuery = createQuery(true, false);
		findAllLazyQuery = createQuery(false, false);
		initValueListQuery();
	}

	@Override
	public void remove(String id) throws ApplicationException {
		T entity = findById(id);
		if (entity != null) {
			remove(entity);
		}
	}

	@Override
	public void remove(T entity) throws ApplicationException {
		entityManager.remove(entity);
	}

	@Override
	public void save(T entity) throws ApplicationException {
		entityManager.persist(entity);
	}

	/**
	 * @param entityManager
	 *            the entityManager to set
	 */
	@PersistenceContext
	public void setEntityManager(EntityManager entityManager) {
		this.entityManager = entityManager;
	}

	@PostConstruct
	protected void startService() {
		preStartService();
		doStartService();
		postStartService();
	}

	@Override
	public T update(T entity) throws ApplicationException {
		return entityManager.merge(entity);
	}

}
